// SPDX-License-Identifier: GPL-2.0-only
/*
 *  linux/lib/vsprintf.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 */

/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/*
 * Wirzenius wrote this portably, Torvalds fucked it up :-)
 */

/*
 * Fri Jul 13 2001 Crutcher Dunnavant <crutcher+kernel@datastacks.com>
 * - changed to provide snprintf and vsnprintf functions
 * So Feb  1 16:51:32 CET 2004 Juergen Quade <quade@hsnr.de>
 * - scnprintf and vscnprintf
 */
#include <stdarg.h>
#include <errno.h>
#include <utils/utils.h>
#include <utils/pagesize.h>
#include <utils/kstrtox.h>
#include <utils/bitmap.h>
#include <utils/math64.h>
#include <seminix/of.h>
#include <seminix/bug.h>

static
int skip_atoi(const char **s)
{
    int i = 0;

    do {
        i = i*10 + *((*s)++) - '0';
    } while (isdigit(**s));

    return i;
}

#define SIGN	1		/* unsigned/signed, must be 1 */
#define LEFT	2		/* left justified */
#define PLUS	4		/* show plus */
#define SPACE	8		/* space if plus */
#define ZEROPAD	16		/* pad with zero, must be 16 == '0' - ' ' */
#define SMALL	32		/* use lowercase in hex (must be 32 == 0x20) */
#define SPECIAL	64		/* prefix hex with "0x", octal with "0" */

enum format_type {
    FORMAT_TYPE_NONE, /* Just a string part */
    FORMAT_TYPE_WIDTH,
    FORMAT_TYPE_PRECISION,
    FORMAT_TYPE_CHAR,
    FORMAT_TYPE_STR,
    FORMAT_TYPE_PTR,
    FORMAT_TYPE_PERCENT_CHAR,
    FORMAT_TYPE_INVALID,
    FORMAT_TYPE_LONG_LONG,
    FORMAT_TYPE_ULONG,
    FORMAT_TYPE_LONG,
    FORMAT_TYPE_UBYTE,
    FORMAT_TYPE_BYTE,
    FORMAT_TYPE_USHORT,
    FORMAT_TYPE_SHORT,
    FORMAT_TYPE_UINT,
    FORMAT_TYPE_INT,
    FORMAT_TYPE_SIZE_T,
    FORMAT_TYPE_PTRDIFF
};

struct printk_spec {
    unsigned int	type;		/* format_type enum */
    signed int	field_width;	/* width of output field */
    unsigned int	flags;	/* flags to number() */
    unsigned int	base;		/* number base, 8, 10 or 16 only */
    signed int	precision;	/* # of digits/chars */
};

#define FIELD_WIDTH_MAX ((1 << 23) - 1)
#define PRECISION_MAX ((1 << 15) - 1)

static
char *number(char *buf, char *end, unsigned long long num,
         struct printk_spec spec)
{
    /* put_dec requires 2-byte alignment of the buffer. */
    char tmp[3 * sizeof(num)] __aligned(2);
    char sign;
    char locase;
    int need_pfx = ((spec.flags & SPECIAL) && spec.base != 10);
    int i;
    bool is_zero = num == 0LL;
    int field_width = spec.field_width;
    int precision = spec.precision;

    /* locase = 0 or 0x20. ORing digits or letters with 'locase'
     * produces same digits or (maybe lowercased) letters */
    locase = (spec.flags & SMALL);
    if (spec.flags & LEFT)
        spec.flags &= ~ZEROPAD;
    sign = 0;
    if (spec.flags & SIGN) {
        if ((signed long long)num < 0) {
            sign = '-';
            num = -(signed long long)num;
            field_width--;
        } else if (spec.flags & PLUS) {
            sign = '+';
            field_width--;
        } else if (spec.flags & SPACE) {
            sign = ' ';
            field_width--;
        }
    }
    if (need_pfx) {
        if (spec.base == 16)
            field_width -= 2;
        else if (!is_zero)
            field_width--;
    }

    /* generate full string in tmp[], in reverse order */
    i = 0;
    if (num < spec.base)
        tmp[i++] = hex_asc_upper[num] | locase;
    else if (spec.base != 10) { /* 8 or 16 */
        int mask = spec.base - 1;
        int shift = 3;

        if (spec.base == 16)
            shift = 4;
        do {
            tmp[i++] = (hex_asc_upper[((unsigned char)num) & mask] | locase);
            num >>= shift;
        } while (num);
    } else { /* base 10 */
        i = put_dec(tmp, num) - tmp;
    }

    /* printing 100 using %2d gives "100", not "00" */
    if (i > precision)
        precision = i;
    /* leading space padding */
    field_width -= precision;
    if (!(spec.flags & (ZEROPAD | LEFT))) {
        while (--field_width >= 0) {
            if (buf < end)
                *buf = ' ';
            ++buf;
        }
    }
    /* sign */
    if (sign) {
        if (buf < end)
            *buf = sign;
        ++buf;
    }
    /* "0x" / "0" prefix */
    if (need_pfx) {
        if (spec.base == 16 || !is_zero) {
            if (buf < end)
                *buf = '0';
            ++buf;
        }
        if (spec.base == 16) {
            if (buf < end)
                *buf = ('X' | locase);
            ++buf;
        }
    }
    /* zero or space padding */
    if (!(spec.flags & LEFT)) {
        char c = ' ' + (spec.flags & ZEROPAD);

        while (--field_width >= 0) {
            if (buf < end)
                *buf = c;
            ++buf;
        }
    }
    /* hmm even more zero padding? */
    while (i <= --precision) {
        if (buf < end)
            *buf = '0';
        ++buf;
    }
    /* actual digits of result */
    while (--i >= 0) {
        if (buf < end)
            *buf = tmp[i];
        ++buf;
    }
    /* trailing space padding */
    while (--field_width >= 0) {
        if (buf < end)
            *buf = ' ';
        ++buf;
    }

    return buf;
}

static
char *special_hex_number(char *buf, char *end, unsigned long long num, int size)
{
    struct printk_spec spec;

    spec.type = FORMAT_TYPE_PTR;
    spec.field_width = 2 + 2 * size;	/* 0x + hex */
    spec.flags = SPECIAL | SMALL | ZEROPAD;
    spec.base = 16;
    spec.precision = -1;

    return number(buf, end, num, spec);
}

static void move_right(char *buf, char *end, unsigned len, unsigned spaces)
{
    size_t size;
    if (buf >= end)	/* nowhere to put anything */
        return;
    size = end - buf;
    if (size <= spaces) {
        memset(buf, ' ', size);
        return;
    }
    if (len) {
        if (len > size - spaces)
            len = size - spaces;
        memmove(buf + spaces, buf, len);
    }
    memset(buf, ' ', spaces);
}

/*
 * Handle field width padding for a string.
 * @buf: current buffer position
 * @n: length of string
 * @end: end of output buffer
 * @spec: for field width and flags
 * Returns: new buffer position after padding.
 */
static
char *widen_string(char *buf, int n, char *end, struct printk_spec spec)
{
    unsigned spaces;

    if (likely(n >= spec.field_width))
        return buf;
    /* we want to pad the sucker */
    spaces = spec.field_width - n;
    if (!(spec.flags & LEFT)) {
        move_right(buf - n, end, n, spaces);
        return buf + spaces;
    }
    while (spaces--) {
        if (buf < end)
            *buf = ' ';
        ++buf;
    }
    return buf;
}

/* Handle string from a well known address. */
static char *string_nocheck(char *buf, char *end, const char *s,
                struct printk_spec spec)
{
    int len = 0;
    int lim = spec.precision;

    while (lim--) {
        char c = *s++;
        if (!c)
            break;
        if (buf < end)
            *buf = c;
        ++buf;
        ++len;
    }
    return widen_string(buf, len, end, spec);
}

static char *err_ptr(char *buf, char *end, void *ptr,
             struct printk_spec spec)
{
    int err = PTR_ERR(ptr);

    /*
     * Somebody passed ERR_PTR(-1234) or some other non-existing
     * Efoo - or perhaps CONFIG_SYMBOLIC_ERRNAME=n. Fall back to
     * printing it as its decimal representation.
     */
    spec.flags |= SIGN;
    spec.base = 10;
    return number(buf, end, err, spec);
}

/* Be careful: error messages must fit into the given buffer. */
static char *error_string(char *buf, char *end, const char *s,
              struct printk_spec spec)
{
    /*
     * Hard limit to avoid a completely insane messages. It actually
     * works pretty well because most error messages are in
     * the many pointer format modifiers.
     */
    if (spec.precision == -1)
        spec.precision = 2 * sizeof(void *);

    return string_nocheck(buf, end, s, spec);
}

/*
 * Do not call any complex external code here. Nested printk()/vsprintf()
 * might cause infinite loops. Failures might break printk() and would
 * be hard to debug.
 */
static const char *check_pointer_msg(const void *ptr)
{
    if (!ptr)
        return "(null)";

    if ((unsigned long)ptr < UTILS_PAGE_SIZE || IS_ERR_VALUE(ptr))
        return "(efault)";

    return NULL;
}

static int check_pointer(char **buf, char *end, const void *ptr,
             struct printk_spec spec)
{
    const char *err_msg;

    err_msg = check_pointer_msg(ptr);
    if (err_msg) {
        *buf = error_string(*buf, end, err_msg, spec);
        return -EFAULT;
    }

    return 0;
}

static noinline_for_stack
char *address_val(char *buf, char *end, const void *addr,
          struct printk_spec spec, const char *fmt)
{
    unsigned long long num;
    int size;

    if (check_pointer(&buf, end, addr, spec))
        return buf;

    switch (fmt[1]) {
    case 'd':
        num = *(const dma_addr_t *)addr;
        size = sizeof(dma_addr_t);
        break;
    case 'p':
    default:
        num = *(const phys_addr_t *)addr;
        size = sizeof(phys_addr_t);
        break;
    }

    return special_hex_number(buf, end, num, size);
}

static noinline_for_stack
char *string(char *buf, char *end, const char *s,
         struct printk_spec spec)
{
    if (check_pointer(&buf, end, s, spec))
        return buf;

    return string_nocheck(buf, end, s, spec);
}

static noinline_for_stack
char *pointer_string(char *buf, char *end,
                const void *ptr,
                struct printk_spec spec)
{
    spec.base = 16;
    spec.flags |= SMALL;
    if (spec.field_width == -1) {
        spec.field_width = 2 * sizeof(ptr);
        spec.flags |= ZEROPAD;
    }

    return number(buf, end, (unsigned long int)ptr, spec);
}

static const char *device_node_name_for_depth(const struct device_node *np, int depth)
{
	for ( ; np && depth; depth--)
		np = np->parent;

	return kbasename(np->full_name);
}

static const struct printk_spec default_str_spec = {
	.field_width = -1,
	.precision = -1,
};

static noinline_for_stack
char *device_node_gen_full_name(const struct device_node *np, char *buf, char *end)
{
	int depth;
	const struct device_node *parent = np->parent;

	/* special case for root node */
	if (!parent)
		return string(buf, end, "/", default_str_spec);

	for (depth = 0; parent->parent; depth++)
		parent = parent->parent;

	for ( ; depth >= 0; depth--) {
		buf = string(buf, end, "/", default_str_spec);
		buf = string(buf, end, device_node_name_for_depth(np, depth),
			     default_str_spec);
	}
	return buf;
}

static noinline_for_stack
char *device_node_string(char *buf, char *end, struct device_node *dn,
             struct printk_spec spec, const char *fmt)
{
    char tbuf[sizeof("xxxx") + 1];
    const char *p;
    int ret;
    char *buf_start = buf;
    struct property *prop;
    bool has_mult, pass;
    static const struct printk_spec num_spec = {
        .flags = SMALL,
        .field_width = -1,
        .precision = -1,
        .base = 10,
    };

    struct printk_spec str_spec = spec;
    str_spec.field_width = -1;

    if (!IS_ENABLED(CONFIG_OF))
        return string(buf, end, "(!OF)", spec);

    if ((unsigned long)dn < UTILS_PAGE_SIZE)
        return string(buf, end, "(null)", spec);

    /* simple case without anything any more format specifiers */
    fmt++;
    if (fmt[0] == '\0' || strcspn(fmt,"fnpPFcC") > 0)
        fmt = "f";

    for (pass = false; strspn(fmt,"fnpPFcC"); fmt++, pass = true) {
        int precision;
        if (pass) {
            if (buf < end)
                *buf = ':';
            buf++;
        }

        switch (*fmt) {
        case 'f':	/* full_name */
            buf = device_node_gen_full_name(dn, buf, end);
            break;
        case 'n':	/* name */
            p = kbasename(of_node_full_name(dn));
            precision = str_spec.precision;
            str_spec.precision = strchrnul(p, '@') - p;
            buf = string(buf, end, p, str_spec);
            str_spec.precision = precision;
            break;
        case 'p':	/* phandle */
            buf = number(buf, end, (unsigned int)dn->phandle, num_spec);
            break;
        case 'P':	/* path-spec */
            p = kbasename(of_node_full_name(dn));
            if (!p[1])
                p = "/";
            buf = string(buf, end, p, str_spec);
            break;
        case 'F':	/* flags */
            tbuf[0] = of_node_check_flag(dn, OF_DYNAMIC) ? 'D' : '-';
            tbuf[1] = of_node_check_flag(dn, OF_DETACHED) ? 'd' : '-';
            tbuf[2] = of_node_check_flag(dn, OF_POPULATED) ? 'P' : '-';
            tbuf[3] = of_node_check_flag(dn, OF_POPULATED_BUS) ? 'B' : '-';
            tbuf[4] = 0;
            buf = string(buf, end, tbuf, str_spec);
            break;
        case 'c':	/* major compatible string */
            ret = of_property_read_string(dn, "compatible", &p);
            if (!ret)
                buf = string(buf, end, p, str_spec);
            break;
        case 'C':	/* full compatible string */
            has_mult = false;
            of_property_for_each_string(dn, "compatible", prop, p) {
                if (has_mult)
                    buf = string(buf, end, ",", str_spec);
                buf = string(buf, end, "\"", str_spec);
                buf = string(buf, end, p, str_spec);
                buf = string(buf, end, "\"", str_spec);

                has_mult = true;
            }
            break;
        default:
            break;
        }
    }

    return widen_string(buf, buf - buf_start, end, spec);
}

static noinline_for_stack
char *pointer(const char *fmt, char *buf, char *end, void *ptr,
          struct printk_spec spec)
{
    switch (*fmt) {
    case 'a':
        return address_val(buf, end, ptr, spec, fmt);
    case 'e':
        /* %pe with a non-ERR_PTR gets treated as plain %p */
        if (!IS_ERR(ptr))
            break;
        return err_ptr(buf, end, ptr, spec);
    case 'O':
        switch (fmt[1]) {
        case 'F':
            return device_node_string(buf, end, ptr, spec, fmt + 1);
        }
        break;
    };

    return pointer_string(buf, end, ptr, spec);
}

/*
 * Helper function to decode printf style format.
 * Each call decode a token from the format and return the
 * number of characters read (or likely the delta where it wants
 * to go on the next call).
 * The decoded token is returned through the parameters
 *
 * 'h', 'l', or 'L' for integer fields
 * 'z' support added 23/7/1999 S.H.
 * 'z' changed to 'Z' --davidm 1/25/99
 * 'Z' changed to 'z' --adobriyan 2017-01-25
 * 't' added for ptrdiff_t
 *
 * @fmt: the format string
 * @type of the token returned
 * @flags: various flags such as +, -, # tokens..
 * @field_width: overwritten width
 * @base: base of the number (octal, hex, ...)
 * @precision: precision of a number
 * @qualifier: qualifier of a number (long, size_t, ...)
 */
static noinline_for_stack
int format_decode(const char *fmt, struct printk_spec *spec)
{
    const char *start = fmt;
    char qualifier;

    /* we finished early by reading the field width */
    if (spec->type == FORMAT_TYPE_WIDTH) {
        if (spec->field_width < 0) {
            spec->field_width = -spec->field_width;
            spec->flags |= LEFT;
        }
        spec->type = FORMAT_TYPE_NONE;
        goto precision;
    }

    /* we finished early by reading the precision */
    if (spec->type == FORMAT_TYPE_PRECISION) {
        if (spec->precision < 0)
            spec->precision = 0;

        spec->type = FORMAT_TYPE_NONE;
        goto qualifier;
    }

    /* By default */
    spec->type = FORMAT_TYPE_NONE;

    for (; *fmt ; ++fmt) {
        if (*fmt == '%')
            break;
    }

    /* Return the current non-format string */
    if (fmt != start || !*fmt)
        return fmt - start;

    /* Process flags */
    spec->flags = 0;

    while (1) { /* this also skips first '%' */
        bool found = true;

        ++fmt;

        switch (*fmt) {
        case '-': spec->flags |= LEFT;    break;
        case '+': spec->flags |= PLUS;    break;
        case ' ': spec->flags |= SPACE;   break;
        case '#': spec->flags |= SPECIAL; break;
        case '0': spec->flags |= ZEROPAD; break;
        default:  found = false;
        }

        if (!found)
            break;
    }

    /* get field width */
    spec->field_width = -1;

    if (isdigit(*fmt))
        spec->field_width = skip_atoi(&fmt);
    else if (*fmt == '*') {
        /* it's the next argument */
        spec->type = FORMAT_TYPE_WIDTH;
        return ++fmt - start;
    }

precision:
    /* get the precision */
    spec->precision = -1;
    if (*fmt == '.') {
        ++fmt;
        if (isdigit(*fmt)) {
            spec->precision = skip_atoi(&fmt);
            if (spec->precision < 0)
                spec->precision = 0;
        } else if (*fmt == '*') {
            /* it's the next argument */
            spec->type = FORMAT_TYPE_PRECISION;
            return ++fmt - start;
        }
    }

qualifier:
    /* get the conversion qualifier */
    qualifier = 0;
    if (*fmt == 'h' || _tolower(*fmt) == 'l' ||
        *fmt == 'z' || *fmt == 't') {
        qualifier = *fmt++;
        if (unlikely(qualifier == *fmt)) {
            if (qualifier == 'l') {
                qualifier = 'L';
                ++fmt;
            } else if (qualifier == 'h') {
                qualifier = 'H';
                ++fmt;
            }
        }
    }

    /* default base */
    spec->base = 10;
    switch (*fmt) {
    case 'c':
        spec->type = FORMAT_TYPE_CHAR;
        return ++fmt - start;

    case 's':
        spec->type = FORMAT_TYPE_STR;
        return ++fmt - start;

    case 'p':
        spec->type = FORMAT_TYPE_PTR;
        return ++fmt - start;

    case '%':
        spec->type = FORMAT_TYPE_PERCENT_CHAR;
        return ++fmt - start;

    /* integer number formats - set up the flags and "break" */
    case 'o':
        spec->base = 8;
        break;

    case 'x':
        spec->flags |= SMALL;
        fallthrough;

    case 'X':
        spec->base = 16;
        break;

    case 'd':
    case 'i':
        spec->flags |= SIGN;
        break;
    case 'u':
        break;

    case 'n':
        /*
         * Since %n poses a greater security risk than
         * utility, treat it as any other invalid or
         * unsupported format specifier.
         */
        fallthrough;

    default:
        WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt);
        spec->type = FORMAT_TYPE_INVALID;
        return fmt - start;
    }

    if (qualifier == 'L')
        spec->type = FORMAT_TYPE_LONG_LONG;
    else if (qualifier == 'l') {
        BUILD_BUG_ON(FORMAT_TYPE_ULONG + SIGN != FORMAT_TYPE_LONG);
        spec->type = FORMAT_TYPE_ULONG + (spec->flags & SIGN);
    } else if (qualifier == 'z') {
        spec->type = FORMAT_TYPE_SIZE_T;
    } else if (qualifier == 't') {
        spec->type = FORMAT_TYPE_PTRDIFF;
    } else if (qualifier == 'H') {
        BUILD_BUG_ON(FORMAT_TYPE_UBYTE + SIGN != FORMAT_TYPE_BYTE);
        spec->type = FORMAT_TYPE_UBYTE + (spec->flags & SIGN);
    } else if (qualifier == 'h') {
        BUILD_BUG_ON(FORMAT_TYPE_USHORT + SIGN != FORMAT_TYPE_SHORT);
        spec->type = FORMAT_TYPE_USHORT + (spec->flags & SIGN);
    } else {
        BUILD_BUG_ON(FORMAT_TYPE_UINT + SIGN != FORMAT_TYPE_INT);
        spec->type = FORMAT_TYPE_UINT + (spec->flags & SIGN);
    }

    return ++fmt - start;
}

static void
set_field_width(struct printk_spec *spec, int width)
{
    spec->field_width = width;
    if (WARN_ONCE(spec->field_width != width, "field width %d too large", width)) {
        spec->field_width = clamp(width, -FIELD_WIDTH_MAX, FIELD_WIDTH_MAX);
    }
}

static void
set_precision(struct printk_spec *spec, int prec)
{
    spec->precision = prec;
    if (WARN_ONCE(spec->precision != prec, "precision %d too large", prec)) {
        spec->precision = clamp(prec, 0, PRECISION_MAX);
    }
}

/**
 * vsnprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @args: Arguments for the format string
 *
 * This function generally follows C99 vsnprintf, but has some
 * extensions and a few limitations:
 *
 *  - ``%n`` is unsupported
 *  - ``%p*`` is handled by pointer()
 *
 * See pointer() or Documentation/core-api/printk-formats.rst for more
 * extensive description.
 *
 * **Please update the documentation in both places when making changes**
 *
 * The return value is the number of characters which would
 * be generated for the given input, excluding the trailing
 * '\0', as per ISO C99. If you want to have the exact
 * number of characters written into @buf as return value
 * (not including the trailing '\0'), use vscnprintf(). If the
 * return is greater than or equal to @size, the resulting
 * string is truncated.
 *
 * If you're not already dealing with a va_list consider using snprintf().
 */
int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
    unsigned long long num;
    char *str, *end;
    struct printk_spec spec = {0};

    /* Reject out-of-range values early.  Large positive sizes are
       used for unknown buffer sizes. */
    if (WARN_ON_ONCE(size > INT_MAX))
        return 0;

    str = buf;
    end = buf + size;

    /* Make sure end is always >= buf */
    if (end < buf) {
        end = ((void *)-1);
        size = end - buf;
    }

    while (*fmt) {
        const char *old_fmt = fmt;
        int read = format_decode(fmt, &spec);

        fmt += read;

        switch (spec.type) {
        case FORMAT_TYPE_NONE: {
            int copy = read;
            if (str < end) {
                if (copy > end - str)
                    copy = end - str;
                memcpy(str, old_fmt, copy);
            }
            str += read;
            break;
        }

        case FORMAT_TYPE_WIDTH:
            set_field_width(&spec, va_arg(args, int));
            break;

        case FORMAT_TYPE_PRECISION:
            set_precision(&spec, va_arg(args, int));
            break;

        case FORMAT_TYPE_CHAR: {
            char c;

            if (!(spec.flags & LEFT)) {
                while (--spec.field_width > 0) {
                    if (str < end)
                        *str = ' ';
                    ++str;

                }
            }
            c = (unsigned char) va_arg(args, int);
            if (str < end)
                *str = c;
            ++str;
            while (--spec.field_width > 0) {
                if (str < end)
                    *str = ' ';
                ++str;
            }
            break;
        }

        case FORMAT_TYPE_STR:
            str = string(str, end, va_arg(args, char *), spec);
            break;

        case FORMAT_TYPE_PTR:
            str = pointer(fmt, str, end, va_arg(args, void *),
                      spec);
            while (isalnum(*fmt))
                fmt++;
            break;

        case FORMAT_TYPE_PERCENT_CHAR:
            if (str < end)
                *str = '%';
            ++str;
            break;

        case FORMAT_TYPE_INVALID:
            /*
             * Presumably the arguments passed gcc's type
             * checking, but there is no safe or sane way
             * for us to continue parsing the format and
             * fetching from the va_list; the remaining
             * specifiers and arguments would be out of
             * sync.
             */
            goto out;

        default:
            switch (spec.type) {
            case FORMAT_TYPE_LONG_LONG:
                num = va_arg(args, long long);
                break;
            case FORMAT_TYPE_ULONG:
                num = va_arg(args, unsigned long);
                break;
            case FORMAT_TYPE_LONG:
                num = va_arg(args, long);
                break;
            case FORMAT_TYPE_SIZE_T:
                if (spec.flags & SIGN)
                    num = va_arg(args, ssize_t);
                else
                    num = va_arg(args, size_t);
                break;
            case FORMAT_TYPE_PTRDIFF:
                num = va_arg(args, ptrdiff_t);
                break;
            case FORMAT_TYPE_UBYTE:
                num = (unsigned char) va_arg(args, int);
                break;
            case FORMAT_TYPE_BYTE:
                num = (signed char) va_arg(args, int);
                break;
            case FORMAT_TYPE_USHORT:
                num = (unsigned short) va_arg(args, int);
                break;
            case FORMAT_TYPE_SHORT:
                num = (short) va_arg(args, int);
                break;
            case FORMAT_TYPE_INT:
                num = (int) va_arg(args, int);
                break;
            default:
                num = va_arg(args, unsigned int);
            }

            str = number(str, end, num, spec);
        }
    }

out:
    if (size > 0) {
        if (str < end)
            *str = '\0';
        else
            end[-1] = '\0';
    }

    /* the trailing null byte doesn't count towards the total */
    return str-buf;

}

/**
 * vscnprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @args: Arguments for the format string
 *
 * The return value is the number of characters which have been written into
 * the @buf not including the trailing '\0'. If @size is == 0 the function
 * returns 0.
 *
 * If you're not already dealing with a va_list consider using scnprintf().
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */
int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
    size_t i;

    i = vsnprintf(buf, size, fmt, args);

    if (likely(i < size))
        return i;
    if (size != 0)
        return size - 1;
    return 0;
}

/**
 * snprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @...: Arguments for the format string
 *
 * The return value is the number of characters which would be
 * generated for the given input, excluding the trailing null,
 * as per ISO C99.  If the return is greater than or equal to
 * @size, the resulting string is truncated.
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */
int snprintf(char *buf, size_t size, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i = vsnprintf(buf, size, fmt, args);
    va_end(args);

    return i;
}

/**
 * scnprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @size: The size of the buffer, including the trailing null space
 * @fmt: The format string to use
 * @...: Arguments for the format string
 *
 * The return value is the number of characters written into @buf not including
 * the trailing '\0'. If @size is == 0 the function returns 0.
 */

int scnprintf(char *buf, size_t size, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i = vscnprintf(buf, size, fmt, args);
    va_end(args);

    return i;
}

/**
 * vsprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @fmt: The format string to use
 * @args: Arguments for the format string
 *
 * The function returns the number of characters written
 * into @buf. Use vsnprintf() or vscnprintf() in order to avoid
 * buffer overflows.
 *
 * If you're not already dealing with a va_list consider using sprintf().
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */
int vsprintf(char *buf, const char *fmt, va_list args)
{
    return vsnprintf(buf, INT_MAX, fmt, args);
}

/**
 * sprintf - Format a string and place it in a buffer
 * @buf: The buffer to place the result into
 * @fmt: The format string to use
 * @...: Arguments for the format string
 *
 * The function returns the number of characters written
 * into @buf. Use snprintf() or scnprintf() in order to avoid
 * buffer overflows.
 *
 * See the vsnprintf() documentation for format string extensions over C99.
 */
int sprintf(char *buf, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i = vsnprintf(buf, INT_MAX, fmt, args);
    va_end(args);

    return i;
}

/**
 * vsscanf - Unformat a buffer into a list of arguments
 * @buf:	input buffer
 * @fmt:	format of buffer
 * @args:	arguments
 */
int vsscanf(const char *buf, const char *fmt, va_list args)
{
    const char *str = buf;
    char *next;
    char digit;
    int num = 0;
    u8 qualifier;
    unsigned int base;
    union {
        long long s;
        unsigned long long u;
    } val;
    s16 field_width;
    bool is_sign;

    while (*fmt) {
        /* skip any white space in format */
        /* white space in format matchs any amount of
         * white space, including none, in the input.
         */
        if (isspace(*fmt)) {
            fmt = skip_spaces(++fmt);
            str = skip_spaces(str);
        }

        /* anything that is not a conversion must match exactly */
        if (*fmt != '%' && *fmt) {
            if (*fmt++ != *str++)
                break;
            continue;
        }

        if (!*fmt)
            break;
        ++fmt;

        /* skip this conversion.
         * advance both strings to next white space
         */
        if (*fmt == '*') {
            if (!*str)
                break;
            while (!isspace(*fmt) && *fmt != '%' && *fmt) {
                /* '%*[' not yet supported, invalid format */
                if (*fmt == '[')
                    return num;
                fmt++;
            }
            while (!isspace(*str) && *str)
                str++;
            continue;
        }

        /* get field width */
        field_width = -1;
        if (isdigit(*fmt)) {
            field_width = skip_atoi(&fmt);
            if (field_width <= 0)
                break;
        }

        /* get conversion qualifier */
        qualifier = -1;
        if (*fmt == 'h' || _tolower(*fmt) == 'l' ||
            *fmt == 'z') {
            qualifier = *fmt++;
            if (unlikely(qualifier == *fmt)) {
                if (qualifier == 'h') {
                    qualifier = 'H';
                    fmt++;
                } else if (qualifier == 'l') {
                    qualifier = 'L';
                    fmt++;
                }
            }
        }

        if (!*fmt)
            break;

        if (*fmt == 'n') {
            /* return number of characters read so far */
            *va_arg(args, int *) = str - buf;
            ++fmt;
            continue;
        }

        if (!*str)
            break;

        base = 10;
        is_sign = false;

        switch (*fmt++) {
        case 'c':
        {
            char *s = (char *)va_arg(args, char*);
            if (field_width == -1)
                field_width = 1;
            do {
                *s++ = *str++;
            } while (--field_width > 0 && *str);
            num++;
        }
        continue;
        case 's':
        {
            char *s = (char *)va_arg(args, char *);
            if (field_width == -1)
                field_width = SHRT_MAX;
            /* first, skip leading white space in buffer */
            str = skip_spaces(str);

            /* now copy until next white space */
            while (*str && !isspace(*str) && field_width--)
                *s++ = *str++;
            *s = '\0';
            num++;
        }
        continue;
        /*
         * Warning: This implementation of the '[' conversion specifier
         * deviates from its glibc counterpart in the following ways:
         * (1) It does NOT support ranges i.e. '-' is NOT a special
         *     character
         * (2) It cannot match the closing bracket ']' itself
         * (3) A field width is required
         * (4) '%*[' (discard matching input) is currently not supported
         *
         * Example usage:
         * ret = sscanf("00:0a:95","%2[^:]:%2[^:]:%2[^:]",
         *		buf1, buf2, buf3);
         * if (ret < 3)
         *    // etc..
         */
        case '[':
        {
            char *s = (char *)va_arg(args, char *);
            DECLARE_BITMAP(set, 256) = {0};
            unsigned int len = 0;
            bool negate = (*fmt == '^');

            /* field width is required */
            if (field_width == -1)
                return num;

            if (negate)
                ++fmt;

            for ( ; *fmt && *fmt != ']'; ++fmt, ++len)
                set_bit((u8)*fmt, set);

            /* no ']' or no character set found */
            if (!*fmt || !len)
                return num;
            ++fmt;

            if (negate) {
                bitmap_complement(set, set, 256);
                /* exclude null '\0' byte */
                clear_bit(0, set);
            }

            /* match must be non-empty */
            if (!test_bit((u8)*str, set))
                return num;

            while (test_bit((u8)*str, set) && field_width--)
                *s++ = *str++;
            *s = '\0';
            ++num;
        }
        continue;
        case 'o':
            base = 8;
            break;
        case 'x':
        case 'X':
            base = 16;
            break;
        case 'i':
            base = 0;
            fallthrough;
        case 'd':
            is_sign = true;
            fallthrough;
        case 'u':
            break;
        case '%':
            /* looking for '%' in str */
            if (*str++ != '%')
                return num;
            continue;
        default:
            /* invalid format; stop here */
            return num;
        }

        /* have some sort of integer conversion.
         * first, skip white space in buffer.
         */
        str = skip_spaces(str);

        digit = *str;
        if (is_sign && digit == '-')
            digit = *(str + 1);

        if (!digit
            || (base == 16 && !isxdigit(digit))
            || (base == 10 && !isdigit(digit))
            || (base == 8 && (!isdigit(digit) || digit > '7'))
            || (base == 0 && !isdigit(digit)))
            break;

        if (is_sign)
            val.s = qualifier != 'L' ?
                simple_strtol(str, &next, base) :
                simple_strtoll(str, &next, base);
        else
            val.u = qualifier != 'L' ?
                simple_strtoul(str, &next, base) :
                simple_strtoull(str, &next, base);

        if (field_width > 0 && next - str > field_width) {
            if (base == 0)
                _parse_integer_fixup_radix(str, &base);
            while (next - str > field_width) {
                if (is_sign)
                    val.s = div_s64(val.s, base);
                else
                    val.u = div_u64(val.u, base);
                --next;
            }
        }

        switch (qualifier) {
        case 'H':	/* that's 'hh' in format */
            if (is_sign)
                *va_arg(args, signed char *) = val.s;
            else
                *va_arg(args, unsigned char *) = val.u;
            break;
        case 'h':
            if (is_sign)
                *va_arg(args, short *) = val.s;
            else
                *va_arg(args, unsigned short *) = val.u;
            break;
        case 'l':
            if (is_sign)
                *va_arg(args, long *) = val.s;
            else
                *va_arg(args, unsigned long *) = val.u;
            break;
        case 'L':
            if (is_sign)
                *va_arg(args, long long *) = val.s;
            else
                *va_arg(args, unsigned long long *) = val.u;
            break;
        case 'z':
            *va_arg(args, size_t *) = val.u;
            break;
        default:
            if (is_sign)
                *va_arg(args, int *) = val.s;
            else
                *va_arg(args, unsigned int *) = val.u;
            break;
        }
        num++;

        if (!next)
            break;
        str = next;
    }

    return num;
}

/**
 * sscanf - Unformat a buffer into a list of arguments
 * @buf:	input buffer
 * @fmt:	formatting of buffer
 * @...:	resulting arguments
 */
int sscanf(const char *buf, const char *fmt, ...)
{
    va_list args;
    int i;

    va_start(args, fmt);
    i = vsscanf(buf, fmt, args);
    va_end(args);

    return i;
}
